from Population import Population
import utility.graph as GR
import copy

class Evolution(object):
	'''
	Questa classe descrive l'evoluzione della popolazione
	'''
	
	def __init__(self, max_it, pop_size, pcross, pmut, Patterns, nCentr):
		'''
		 - max_it: numero massimo di iterazioni previste
		 - pop_size: numero di individui nella popolazione
		'''
		self._max_it = max_it
		self._pop_size = pop_size
		self._pcross = pcross
		self._pmut = pmut
		self._patterns = Patterns
		self._nCentr = nCentr
		
		self._population = Population(self.getPopSize(), self.getPatterns(), nCentr)
		self._population.initRandomChromosomes()
		
		### Calcolo i valori massimo e minio che assume un vettore lungo ciascuna dimensione
		self.calculatePatternLimits()
		
	def calculatePatternLimits(self):
		'''
		Calcola i valori massimi di x_i, dove x_i e' il valore di un punto del data set lungo
		la dimensione i-esima
		'''
		patterns = self.getPatterns()
		D = len(patterns[0])
		
		### Inizializzo gli array
		max = range(D)
		min = range(D)
		
		for i in range(D):
			### Per ciascuna dimensione
			max[i] = None
			min[i] = None
			for p in patterns:
				### Aggiorno il valore massimo
				if (max[i] == None or p[i] > max[i]):
					max[i] = p[i]
				
				### Aggiorno il valore minimo
				if (min[i] == None or p[i] < min[i]):
					min[i] = p[i]
					
		self.getPopulation()._dataset_min = min
		self.getPopulation()._dataset_max = max
	
	def getPatterns(self):
		'''
		Restituisce la lista dei pattern (punti) da clusterizzare
		'''
		return self._patterns
		
	def getPcross(self):
		'''
		Restituisce la probabilita' di crossover
		'''
		return self._pcross
		
	def getPmut(self):
		'''
		Restituisce la probabilita' di mutazione
		'''
		return self._pmut
		
	def getPopulation(self):
		'''
		Restituisce l'oggetto Population
		'''
		return self._population
		
	def getMaxIterations(self):
		'''
		Restituisce il numero massimo di iterazioni previste
		'''
		return self._max_it
		
	def getPopSize(self):
		'''
		Restituisce il numero di individui nella popolazione
		'''
		return self._pop_size
		
	def getNCentroids(self):
		'''
		Ritorna il numero di centroidi scelto
		'''
		return self._nCentr
		
	def nextGeneration(self):
		'''
		Effettua tutte le operazioni necessarie per creare la nuova generazione:
		 - effettuare le operazioni di clusterizzazione su TUTTE le soluzioni (ovvero su tutti i cromosomi) tramite l'algoritmo K-means
		 - calcola i valori di metric per ciascun cromosoma
		 - effettua le operazioni di selezione, crossover e mutazione per la generazione dei prossimi individui
		'''
		### Effettua la clusterizzazione dei dati, per ciascun cromosoma (cioe' per ciascuna soluzione)
		### Prima calcolo le distanze di ciascun pattern a ciascun centroide
		### E conseguentemente assegno ciascun pattern al centroide piu' vicino
		#~ self.getPopulation().metricDivision()
		#~ 
		#~ ### Quindi ricalcolo la posizione dei centroidi
		#~ self.getPopulation().clusterize()
		
		### CORREZIONE impi Ne avevamo parlato ----- 
		#   lascio ache l'altra e ne discutiamo.
		self.getPopulation().clusterize()
		
		self.getPopulation().metricDivision()
		###-----------------------------------------
			
		### Calcola Metric e fitness per ogni cromosoma, richiesti per la parte genetica dell'algoritmo
		self.getPopulation().computeMetric()
		
		### Prepara la popolazione per la selezione
		self.getPopulation().prepareForSelection()
				
		### Aggiorna la popolazione
		self._population = self.getPopulation().getNextGeneration(self.getPcross(), self.getPmut())
	
	def evolve(self,pl,grCentri,grOn):
		'''
		Metodo principale
		Ritorna: 
		L'indice del miglior cromosoma,  il vettore con 
		l'andamento della fitness massima assoluta,
		e il cromosoma realtivo al miglior individuo.
		maxAssIndxChrom,fmaxAss, maxAssChrom
		'''		
		
		fmaxAss = []
		maxAss = None
		
		### Calcolo le distanze con i centroidi estratti a caso
		#   prima di iniziare la clusterizzazione
		self.getPopulation().metricDivision()
		
		for it in range(self.getMaxIterations()):
			print "iterazione ", it
			### Genera gli individui della prossima generazione
			self.nextGeneration()
			
			### Grafica le caratteristiche del migliore individuo
			fTemp = []
			for i in range(self.getPopSize()):
				
				fTemp.append(self.getPopulation().getChromosomes()[i].getFitness())
			
			 
			### prendo il massimo di questa iterazione	-----------------------
			fmaxTmp = max(fTemp)
			### e il suo indice che indica quale individuo ha la fitness massima			
			fmaxIndx = fTemp.index(fmaxTmp)  
			###-----------------------------------------------------------------
			
			### Massimo tra tutte le iterazioni -------------------------------------------
			if maxAss == None or fmaxTmp > maxAss:
				
				maxAss = fmaxTmp
				print "F Max sostituita", maxAss
				### questo per avere una visione globale dell'ottimizzazione 
				fmaxAss.append(fmaxTmp)
				### Salvo il cromosoma relativo al miglior individuo
				maxAssChrom = copy.deepcopy(self.getPopulation().getChromosomes()[fmaxIndx])
				### Indice che identifica il miglior indiiduo
				maxAssIndx = fmaxIndx
				iterazioneN = it
			else:
				fmaxAss.append(maxAss)
				print "F Max mantenuta", maxAss
				
			print "Attuale migliore individuo: ",maxAssIndx
			print "Trovato all'iterazione: ",iterazioneN
			print " " 
			### ------------------------------------------------------------------------------	
			
			if (grOn == "on"):
				### Aggiorna il grafico tenendo d'occhio solo l'individuo 0
				#   Tanto per vedere come funziona
				#   NOTA: Possimao decidere anche di toglierla la parte grafica in realtime
				Z = []
				for j in range(self.getNCentroids()):
					Z.append(self.getPopulation().getChromosomes()[maxAssIndx].getCentroid(j))
				GR.updateGraph(pl,grCentri,Z,self.getNCentroids())
		
		### Calcola un'ultima volta il valore Metric, per selezionare il cromosoma migliore
		#~ self.getPopulation().computeMetric()
		
		return maxAssIndx,fmaxAss, maxAssChrom
